function defaultToast(icon: any, title: string, callback?: Function, duration: number = 1500) {
    uni.showToast({
        title,
        icon: icon,
        duration,
        mask: true,
    })
    if (callback) {
        setTimeout(() => {
            callback()
        }, duration)
    }
}
interface loadingObject {
    title: string
    do: Function
    mask: boolean
}
/**
 * @description 消息提示框
 * @param String title 提示内容
 * @param String icon 可选值 success loading 默认 none
 * @param Number duration 多少毫秒后隐藏
 */
export const $toast: any = {
    info: function (title: string, callback?: Function, duration: number = 1500) {
        defaultToast('none', title, callback, duration)
    },
    fail: function (title: string, callback: Function, duration: number = 1500) {
        defaultToast('error', title, callback, duration)
    },
    success: function (title: string, callback: Function, duration: number = 1500) {
        defaultToast('success', title, callback, duration)
    },
}

export const $loading: any = {
    show: function (obj: loadingObject): void {
        if (!obj.title) {
            obj.title = '加载中'
        }
        if (obj.do) {
            obj.do()
        }
        uni.showLoading({ title: obj.title, mask: obj.mask || true })
    },
    hide: function (obj: loadingObject): void {
        uni.hideLoading()
    },
}

export const $util: any = {
    /**
     * 返回上n页
     * @param defaultUrl: 如果页面不存在，跳转到指定页面，默认为首页
     * @param num: 指定页数，默认为1
     */
    back(defaultUrl: string = '/pages/index/index', num: number = 1): void {
        let pages = getCurrentPages()
        if (pages.length <= num) {
            if (defaultUrl === '/pages/index/index') {
                uni.reLaunch({
                    url: defaultUrl,
                })
            } else {
                uni.redirectTo({
                    url: defaultUrl,
                })
            }
        } else {
            uni.navigateBack({
                delta: num,
            })
        }
    },
    /**
     * 获取随机数字
     */
    getRandomCode: function (length: number) {
        if (length > 0) {
            let data: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
            let nums = ''
            for (let i = 0; i < length; i++) {
                let r = parseInt(String(Math.random() * 10))
                nums += data[r]
            }
            return nums
        } else {
            return false
        }
    },
    /**
     * 获取头像的文字
     */
    getAvatarText: function (name: string): string {
        if (name) {
            return name.charAt(0)
        }
        return ''
    },
    /**
     * 前往掌业宝app
     */
    openApp: function (): void {
        // #ifdef H5
        window.location.href = 'https://www.91ready.com/#/app'
        // #endif
    },

    /**
     * 查找树状数据中符合条件的数据
     * @param tree
     * @param func
     * @returns {*|null}
     */
    findTreeNode: function (tree: Array<any>, func: Function) {
        for (const node of tree) {
            if (func(node)) return node
            if (node.children) {
                const res = this.findTreeNode(node.children, func)
                if (res) return res
            }
        }
        return null
    },
    /**
     * 根据value获取key
     */
    findKey: function (obj: any, value: string) {
        for (const key in obj) {
            if (obj[key] == value) return key
        }
        return null
    },

    /**
     * @description 深度克隆
     * @param {object} obj 需要深度克隆的对象
     * @param cache 缓存
     * @returns {*} 克隆后的对象或者原值（不是对象）
     */
    deepClone: function (obj: any, cache = new WeakMap()) {
        if (obj === null || typeof obj !== 'object') return obj
        if (cache.has(obj)) return cache.get(obj)
        let clone
        if (obj instanceof Date) {
            clone = new Date(obj.getTime())
        } else if (obj instanceof RegExp) {
            clone = new RegExp(obj)
        } else if (obj instanceof Map) {
            clone = new Map(Array.from(obj, ([key, value]) => [key, this.deepClone(value, cache)]))
        } else if (obj instanceof Set) {
            clone = new Set(Array.from(obj, (value) => this.deepClone(value, cache)))
        } else if (Array.isArray(obj)) {
            clone = obj.map((value) => this.deepClone(value, cache))
        } else if (Object.prototype.toString.call(obj) === '[object Object]') {
            clone = Object.create(Object.getPrototypeOf(obj))
            cache.set(obj, clone)
            for (const [key, value] of Object.entries(obj)) {
                clone[key] = this.deepClone(value, cache)
            }
        } else {
            clone = Object.assign({}, obj)
        }
        cache.set(obj, clone)
        return clone
    },

    /**
     * @description JS对象深度合并
     * @param {object} target 需要拷贝的对象
     * @param {object} source 拷贝的来源对象
     * @returns {object|boolean} 深度合并后的对象或者false（入参有不是对象）
     */
    deepMerge: function (target: any = {}, source: any = {}) {
        target = this.deepClone(target)
        if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target
        const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target)
        for (const prop in source) {
            if (!source.hasOwnProperty(prop)) continue
            const sourceValue = source[prop]
            const targetValue = merged[prop]
            if (sourceValue instanceof Date) {
                merged[prop] = new Date(sourceValue)
            } else if (sourceValue instanceof RegExp) {
                merged[prop] = new RegExp(sourceValue)
            } else if (sourceValue instanceof Map) {
                merged[prop] = new Map(sourceValue)
            } else if (sourceValue instanceof Set) {
                merged[prop] = new Set(sourceValue)
            } else if (typeof sourceValue === 'object' && sourceValue !== null) {
                merged[prop] = this.deepMerge(targetValue, sourceValue)
            } else {
                merged[prop] = sourceValue
            }
        }
        return merged
    },
    /**
     * @description 去除空格
     * @param String str 需要去除空格的字符串
     * @param String pos both(左右)|left|right|all 默认both
     */
    trim: function (str: string, pos: string = 'both'): string {
        str = String(str)
        if (pos == 'both') {
            return str.replace(/^\s+|\s+$/g, '')
        }
        if (pos == 'left') {
            return str.replace(/^\s*/, '')
        }
        if (pos == 'right') {
            return str.replace(/(\s*$)/g, '')
        }
        if (pos == 'all') {
            return str.replace(/\s+/g, '')
        }
        return str
    },
    /**
     * @description 如果value小于min，取min；如果value大于max，取max
     * @param {number} min
     * @param {number} max
     * @param {number} value
     */
    range: function (min: number = 0, max: number = 0, value: number = 0): number {
        return Math.max(min, Math.min(max, Number(value)))
    },

    /**
     * @description 进行延时，以达到可以简写代码的目的 比如: await uni.$u.sleep(20)将会阻塞20ms
     * @param {number} value 堵塞时间 单位ms 毫秒
     * @returns {Promise} 返回promise
     */
    sleep: function (value: number = 30) {
        return new Promise((resolve): void => {
            setTimeout((): void => {
                resolve(null)
            }, value)
        })
    },
    /**
     * @description 运行期判断平台
     * @returns {string} 返回所在平台(小写)
     * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
     */
    os: function (): string {
        return uni.getSystemInfoSync().platform.toLowerCase()
    },
    /**
     * @description 获取系统信息同步接口
     * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
     */
    sys: function () {
        return uni.getSystemInfoSync()
    },

    /**
     * @description 取一个区间数
     * @param {Number} min 最小值
     * @param {Number} max 最大值
     */
    random: function (min: number, max: number): number {
        if (min >= 0 && max > 0 && max >= min) {
            const gab = max - min + 1
            return Math.floor(Math.random() * gab + min)
        }
        return 0
    },

    /**
     * @param {Number} len uuid的长度
     * @param {Boolean} firstU 将返回的首字母置为"u"
     * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
     */
    guid: function (len: number = 32, firstU: boolean = true, radix: any = null): string {
        const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
        const uuid = []
        radix = radix || chars.length

        if (len) {
            // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
            for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)]
        } else {
            let r
            // rfc4122标准要求返回的uuid中,某些位为固定的字符
            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
            uuid[14] = '4'

            for (let i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | (Math.random() * 16)
                    uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r]
                }
            }
        }
        // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
        if (firstU) {
            uuid.shift()
            return `u${uuid.join('')}`
        }
        return uuid.join('')
    },
    /**
     * @description 对象转url参数
     * @param {object} data,对象
     * @param {Boolean} isPrefix,是否自动加上"?"
     * @param {string} arrayFormat 规则 indices|brackets|repeat|comma
     */
    queryParams: function (data: any = {}, isPrefix: boolean = true, arrayFormat: string = 'brackets') {
        const prefix: string = isPrefix ? '?' : ''
        const _result = []
        if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
        for (const key in data) {
            const value = data[key]
            // 去掉为空的参数
            if (['', undefined, null].indexOf(value) >= 0) {
                continue
            }
            // 如果值为数组，另行处理
            if (value.constructor === Array) {
                // e.g. {ids: [1, 2, 3]}
                switch (arrayFormat) {
                    case 'indices':
                        // 结果: ids[0]=1&ids[1]=2&ids[2]=3
                        for (let i = 0; i < value.length; i++) {
                            _result.push(`${key}[${i}]=${value[i]}`)
                        }
                        break
                    case 'brackets':
                        // 结果: ids[]=1&ids[]=2&ids[]=3
                        value.forEach((_value) => {
                            _result.push(`${key}[]=${_value}`)
                        })
                        break
                    case 'repeat':
                        // 结果: ids=1&ids=2&ids=3
                        value.forEach((_value) => {
                            _result.push(`${key}=${_value}`)
                        })
                        break
                    case 'comma':
                        // 结果: ids=1,2,3
                        let commaStr = ''
                        value.forEach((_value) => {
                            commaStr += (commaStr ? ',' : '') + _value
                        })
                        _result.push(`${key}=${commaStr}`)
                        break
                    default:
                        value.forEach((_value) => {
                            _result.push(`${key}[]=${_value}`)
                        })
                }
            } else {
                _result.push(`${key}=${value}`)
            }
        }
        return _result.length ? prefix + _result.join('&') : ''
    },

    /**
     * @description 获取当前页面路径
     */
    page: function (): string {
        const pages = getCurrentPages()
        // 某些特殊情况下(比如页面进行redirectTo时的一些时机)，pages可能为空数组
        return `/${pages[pages.length - 1]?.route ?? ''}`
    },

    /**
     * @description 获取当前路由栈实例数组
     */
    pages: function () {
        return getCurrentPages()
    },

    /**
     * 获取页面历史栈指定层实例
     * @param back {number} [0] - 0或者负数，表示获取历史栈的哪一层，0表示获取当前页面实例，-1 表示获取上一个页面实例。默认0。
     */
    getHistoryPage: function (back: number = 0) {
        const pages = getCurrentPages()
        const len: number = pages.length
        return pages[len - 1 + back]
    },

    /**
     * 切换Api环境
     */
    toDev: function () {
        let wechatEnv
        // #ifdef MP-WEIXIN
        wechatEnv = uni.getAccountInfoSync().miniProgram.envVersion
        // #endif
        if (wechatEnv === 'trial' || wechatEnv === 'develop' || process.env.NODE_ENV === 'development') {
            const itemList = [
                { key: 'production', name: '正式环境' },
                { key: 'test', name: '开发环境:22' },
                { key: 'development', name: '开发环境:26' },
            ]
            uni.showActionSheet({
                title: '当前:' + uni.getStorageSync('env'),
                itemList: itemList.map((li) => li.name),
                success: ({ tapIndex }) => {
                    uni.setStorageSync('env', itemList[tapIndex].key)
                    this.$toast.info('设置成功,请销毁程序进程,重新进入')
                },
            })
        }
    },
}

export const $test: any = {
    /**
     * 验证电子邮箱格式
     */
    email: function (value: any) {
        return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
    },

    /**
     * 验证手机格式
     */
    mobile: function (value: any) {
        return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
    },

    /**
     * 验证URL格式
     */
    url: function (value: any) {
        return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/.test(
            value
        )
    },

    /**
     * 验证日期格式
     */
    date: function (value: any) {
        if (!value) return false
        // 判断是否数值或者字符串数值(意味着为时间戳)，转为数值，否则new Date无法识别字符串时间戳
        if (this.number(value)) value = +value
        return !/Invalid|NaN/.test(new Date(value).toString())
    },

    /**
     * 验证ISO类型的日期格式
     */
    dateISO: function (value: any) {
        return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
    },

    /**
     * 验证十进制数字
     */
    number: function (value: any) {
        return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
    },

    /**
     * 验证字符串
     */
    string: function (value: any) {
        return typeof value === 'string'
    },

    /**
     * 验证整数
     */
    digits: function (value: any) {
        return /^\d+$/.test(value)
    },

    /**
     * 验证身份证号码
     */
    idCard: function (value: any) {
        return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)
    },

    /**
     * 是否车牌号
     */
    carNo: function (value: any) {
        // 新能源车牌
        const xreg =
            /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
        // 旧车牌
        const creg =
            /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
        if (value.length === 7) {
            return creg.test(value)
        }
        if (value.length === 8) {
            return xreg.test(value)
        }
        return false
    },

    /**
     * 金额,只允许2位小数
     */
    amount: function (value: any) {
        // 金额，只允许保留两位小数
        return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
    },

    /**
     * 中文
     */
    chinese: function (value: any) {
        const reg = /^[\u4e00-\u9fa5]+$/gi
        return reg.test(value)
    },

    /**
     * 只能输入字母
     */
    letter: function (value: any) {
        return /^[a-zA-Z]*$/.test(value)
    },

    /**
     * 只能是字母或者数字
     */
    enOrNum: function (value: any) {
        // 英文或者数字
        const reg = /^[0-9a-zA-Z]*$/g
        return reg.test(value)
    },

    /**
     * 验证是否包含某个值
     */
    contains: function (value: any, param: any) {
        return value.indexOf(param) >= 0
    },

    /**
     * 验证一个值范围[min, max]
     */
    range: function (value: any, param: any) {
        return value >= param[0] && value <= param[1]
    },

    /**
     * 验证一个长度范围[min, max]
     */
    rangeLength: function (value: any, param: any) {
        return value.length >= param[0] && value.length <= param[1]
    },

    /**
     * 是否固定电话
     */
    landline: function (value: any) {
        const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
        return reg.test(value)
    },

    /**
     * 判断是否为空
     */
    empty: function (value: any) {
        switch (typeof value) {
            case 'undefined':
                return true
            case 'string':
                if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
                break
            case 'boolean':
                if (!value) return true
                break
            case 'number':
                if (value === 0 || isNaN(value)) return true
                break
            case 'object':
                if (value === null || value.length === 0) return true
                for (const i in value) {
                    return false
                }
                return true
        }
        return false
    },

    /**
     * 是否json字符串
     */
    jsonString: function (value: any) {
        if (typeof value === 'string') {
            try {
                const obj = JSON.parse(value)
                if (typeof obj === 'object' && obj) {
                    return true
                }
                return false
            } catch (e) {
                return false
            }
        }
        return false
    },

    /**
     * 是否数组
     */
    array: function (value: any) {
        if (typeof Array.isArray === 'function') {
            return Array.isArray(value)
        }
        return Object.prototype.toString.call(value) === '[object Array]'
    },

    /**
     * 是否对象
     */
    object: function (value: any) {
        return Object.prototype.toString.call(value) === '[object Object]'
    },

    /**
     * 是否短信验证码
     */
    code: function (value: any, len: number = 6) {
        return new RegExp(`^\\d{${len}}$`).test(value)
    },

    /**
     * 是否函数方法
     * @param {Object} value
     */
    func: function (value: any) {
        return typeof value === 'function'
    },

    /**
     * 是否promise对象
     * @param {Object} value
     */
    promise: function (value: any) {
        return this.object(value) && this.func(value.then) && this.func(value.catch)
    },

    /** 是否图片格式
     * @param {Object} value
     */
    image: function (value: any) {
        const newValue = value.split('?')[0]
        const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
        return IMAGE_REGEXP.test(newValue)
    },

    /**
     * 是否视频格式
     * @param {Object} value
     */
    video: function (value: any) {
        const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
        return VIDEO_REGEXP.test(value)
    },

    /**
     * 是否为正则对象
     * @param {Object}
     * @return {Boolean}
     */
    regExp: function (o: any) {
        return o && Object.prototype.toString.call(o) === '[object RegExp]'
    },
}

/*判断运行环境*/

export const userAgent = {
    // #ifdef H5
    isIPhone: /(iPhone|iPad|iPod|iOS)/i.test(window.navigator.userAgent),
    isAndroid: /android/i.test(window.navigator.userAgent),
    isWeiXin: /MicroMessenger/i.test(window.navigator.userAgent),
    isDingTalk: /DingTalk/i.test(window.navigator.userAgent),
    isZybIos: /zyb_ios/i.test(window.navigator.userAgent),
    isZybAndroid: /zyb_android/i.test(window.navigator.userAgent),
    // #endif
    // #ifndef H5
    // @ts-ignore
    isIPhone: false,
    // @ts-ignore
    isAndroid: false,
    // @ts-ignore
    isWeiXin: false,
    // @ts-ignore
    isDingTalk: false,
    // @ts-ignore
    isZybIos: false,
    // @ts-ignore
    isZybAndroid: false,
    // #endif
}
